package code.refactoring.Controller;

import java.util.InputMismatchException;
import code.refactoring.Model.*;
import code.refactoring.View.*;

public class GameLogic {

	private static final int MOVE_BY_ONE = 1;
	private static final int MOVING_WITH_ONE_IN_DIRECTION = 1;
	private static final int TO_MATCH_ARRAY_CORDINATES = 1;
	private static final int FREE_SPACE_FOR_DASHES = 2;
	private static final int MIN_GAMEBOARD_DOT_POSSITION = 1;
	private static final int TURN_OF_THE_SECOND_PLAYER = 1;
	private static final int TURN_OF_THE_FIRST_PLAYER = 0;
	private Board gameBoard;
	private FieldDrawer fd = new FieldDrawer();
	private Player playerOne = new Player();
	private Player playerTwo = new Player();
	private int availableMoves;

	int lastMoveDashRowCoordinate;
	int lastMoveDashColumnCoordinate;

	/**
	 * Handles the Logic of the game
	 */
	public void play() {
		initializeGame();
		// a variable for defining which player's turn is it
		int turn = 0;
		while (!isGameOver()) {
			fd.draw();
			if (turn == TURN_OF_THE_FIRST_PLAYER) {
				turn = makeTurn(playerOne, TURN_OF_THE_FIRST_PLAYER);
			} else {
				turn = makeTurn(playerTwo, TURN_OF_THE_SECOND_PLAYER);
			}
		}
		// What happens when isGameOver() becomes true:
		fd.draw();

		if (playerOne.getPoints() > playerTwo.getPoints()) {
			System.out.println("The winner is " + playerOne.getName() + "!");
		}

		if (playerOne.getPoints() < playerTwo.getPoints()) {
			System.out.println("The winner is " + playerTwo.getName() + "!");
		}

		if (playerOne.getPoints() == playerTwo.getPoints()) {
			System.out.println("The game ended in a draw!");
		}
	}

	/**
	 * Process the logic of making the Turn.
	 * 
	 * @param currentPlayer
	 *            - object of Player type representing the player which must
	 *            make the turn
	 * @param currentPlayerTurn
	 *            - integer, for defining which player's turn is it
	 * @return integer, for defining which player's turn is it. if a point is
	 *         scored the player is the same
	 */
	private int makeTurn(Player currentPlayer, int currentPlayerTurn) {
		System.out.println(currentPlayer.getName() + ", it's your turn now.");
		makeMove();
		availableMoves--;
		// if playerOne scores a point we increment his points and
		// retain his turn
		if (isPoint(currentPlayer)) {
			// return the same currentPlayerTurn
		} else {

			currentPlayerTurn ^= (TURN_OF_THE_FIRST_PLAYER ^ TURN_OF_THE_SECOND_PLAYER);
		}
		return currentPlayerTurn;
	}

	/**
	 * Initialize components of the game: - Creates players - Creates game board
	 * - sets the game board to the field drawer - sets available moves
	 */
	private void initializeGame() {
		createPlayer(playerOne);
		createPlayer(playerTwo);
		gameBoard = new Board();
		fd.setGameBoard(gameBoard);
		// calculate the number of all moves in the current game
		calculateAvailableMoves(gameBoard.getSize());
	}

	/**
	 * Create new player by setting mark and Name of the player
	 * 
	 * @return a new Object of type Player
	 */
	private void createPlayer(Player p) {
		char mark;
		do {
			mark = AskUser.askUserForMark();
		} while (!checkMark(mark));
		String name;
		do {
			name = AskUser.askUserForName();
		} while (!checkName(name));
		p.setMark(mark);
		p.setName(name);
	}

	/**
	 * Check if the name of the player is not already taken
	 * 
	 * @param name
	 *            - name of player (One and Two)
	 * @return - true if name is free, false if it's taken
	 */
	public boolean checkName(String name) {
		if (checkNameDuplicity(name)) {
			System.out.println("This nickname is already taken.");
			return false;
		}
		if (name != null && name != "") {
			return true;
		}
		return false;
	}

	private boolean checkNameDuplicity(String name) {
		if (!playerOne.getName().equalsIgnoreCase("newplayer")) {
			if (playerOne.getName().equals(name)) {
				return true;
			}
		}
		return false;
	}

	private boolean checkMark(char mark) {
		if (checkMarkDuplicity(mark)) {
			System.out.println("This mark is already taken.");
			return false;
		}
		if (!Character.isAlphabetic(mark)) {
			System.out.println("Player's mark must be a latin letter!");
			return false;
		}
		return true;
	}

	/**
	 * Check if mark is Free and not already taken
	 * 
	 * @param mark
	 *            char symbol representing the mark of the player
	 * 
	 * @return - true if the mark is not in use, false if it's taken
	 */
	private boolean checkMarkDuplicity(char mark) {
		if (Character.isAlphabetic(playerOne.getMark())
				&& playerOne.getMark() == mark) {
			return true;
		}
		return false;
	}

	/**
	 * Check if there are still moves that can be made or the game is over
	 * 
	 * @return boolean value - true if it's over, false if it's not over
	 */
	private boolean isGameOver() {
		if (this.availableMoves == 0)
			return true;
		return false;
	}

	/**
	 * Method representing the logic of making a move
	 */
	private void makeMove() {
		int row = 0, col = 0;
		char side = ' ';
		boolean isMoveMade = false;
		// do the cycle until the player makes a legal move
		while (isMoveMade == false) {
			boolean correctMove = false;
			try {
				do {
					row = AskUser.askUserForRow();
					correctMove = checkForCorrectMove(row);
				} while (!correctMove);
				correctMove = false;
				do {
					col = AskUser.askUserForColumn();
					correctMove = checkForCorrectMove(col);
				} while (!correctMove);
				side = AskUser.askUserForDirection();
			} catch (InputMismatchException e) {
				System.out.println("Invalid data input,try again !");
			}
			// modifying the coordinates so that they match our gameboard's
			// coordinates
			int newRow = modifyCoordinate(row);
			int newCol = modifyCoordinate(col);
			if (isAcceptableMove(newRow, newCol, side)) {
				gameBoard.saveMove(newRow, newCol, side);
				isMoveMade = true;
				// set coordinate for the last dash made
				setLastDashCoordinates(newRow, newCol, side);
			} else {
				System.out
						.println("This move is not available. Please try again!");
			}
		}
	}

	/**
	 * Check if the selected move is possible
	 * 
	 * @param row
	 *            - selected point's row
	 * @param col
	 *            - selected point's column
	 * @param side
	 *            - selected direction of the move ( down, up,left or right)
	 * @return boolean value - true if the move is correct, false if it's
	 *         unacceptable move
	 */
	private boolean isAcceptableMove(int row, int col, char side) {
		side = Character.toUpperCase(side);
		if (side == 'U') {
			if (gameBoard.getDataForCell(--row, col) == ' ')
				return true;
		} else if (side == 'D') {
			if (gameBoard.getDataForCell(++row, col) == ' ')
				return true;
		} else if (side == 'R') {
			if (gameBoard.getDataForCell(row, ++col) == ' ')
				return true;
		} else if (side == 'L') {
			if (gameBoard.getDataForCell(row, --col) == ' ')
				return true;
		}
		return false;
	}

	/**
	 * Check if the player has scored a point
	 * 
	 * @param p
	 *            - object of type player, which is representing the current
	 *            player
	 * @return boolean value - true if the current player has scored a point,
	 *         false if he didn't scored
	 */
	private boolean isPoint(Player p) {
		// call the checkForSquares method to see if a point is made
		if (checkForSquares(p)) {
			return true;
		}
		return false;
	}

	/**
	 * Check if the last move the player made, forms a square. Logic of the
	 * method is depending on the type of the dash to check only 2 squares. the
	 * dash is a side in those squares if dash row is even dash looks like this:
	 * '-' so we need to check up and down square, if dash row is odd dash looks
	 * like this: '|', so we need to check left and right square. if the dash is
	 * a border of the game field we check only one square again depending on
	 * the type of the dash.
	 * 
	 * @param p
	 *            - object of type player, which is representing the current
	 *            player
	 * @return boolean value - returns true only if there's a new square made,
	 *         and a point was made
	 */
	private boolean checkForSquares(Player p) {
		if (lastMoveDashRowCoordinate % 2 == 0) {
			if (lastMoveDashRowCoordinate == 0) {
				// check only down square
				if (checkSidesOfASquare(lastMoveDashRowCoordinate,
						lastMoveDashColumnCoordinate, 'D')) {
					gameBoard.scoredPointLogic(p, lastMoveDashRowCoordinate
							+ MOVE_BY_ONE, lastMoveDashColumnCoordinate);
					return true;
				}
			} else if (lastMoveDashRowCoordinate == gameBoard.getRows()
					- MOVE_BY_ONE) {
				// check only upper square
				if (checkSidesOfASquare(lastMoveDashRowCoordinate,
						lastMoveDashColumnCoordinate, 'U')) {
					gameBoard.scoredPointLogic(p, lastMoveDashRowCoordinate
							- MOVE_BY_ONE, lastMoveDashColumnCoordinate);
					return true;
				}
			} else {
				boolean hasScoredAPoint = false;
				// check upper square
				if (checkSidesOfASquare(lastMoveDashRowCoordinate,
						lastMoveDashColumnCoordinate, 'U')) {
					gameBoard.scoredPointLogic(p, lastMoveDashRowCoordinate
							- MOVE_BY_ONE, lastMoveDashColumnCoordinate);
					hasScoredAPoint = true;
				}
				// check down square
				if (checkSidesOfASquare(lastMoveDashRowCoordinate,
						lastMoveDashColumnCoordinate, 'D')) {
					gameBoard.scoredPointLogic(p, lastMoveDashRowCoordinate
							+ MOVE_BY_ONE, lastMoveDashColumnCoordinate);
					hasScoredAPoint = true;
				}
				if (hasScoredAPoint) {
					return true;
				}
			}
		} else {
			if (lastMoveDashColumnCoordinate == 0) {
				// check right square
				if (checkSidesOfASquare(lastMoveDashRowCoordinate,
						lastMoveDashColumnCoordinate, 'R')) {
					gameBoard.scoredPointLogic(p, lastMoveDashRowCoordinate,
							lastMoveDashColumnCoordinate + MOVE_BY_ONE);
					return true;
				}
			} else if (lastMoveDashColumnCoordinate == gameBoard.getColumns() - 1) {
				// check left square
				if (checkSidesOfASquare(lastMoveDashRowCoordinate,
						lastMoveDashColumnCoordinate, 'L')) {
					gameBoard.scoredPointLogic(p, lastMoveDashRowCoordinate,
							lastMoveDashColumnCoordinate - MOVE_BY_ONE);
					return true;
				}
			} else {
				boolean hasScoredAPoint = false;
				// check left square
				if (checkSidesOfASquare(lastMoveDashRowCoordinate,
						lastMoveDashColumnCoordinate, 'L')) {
					gameBoard.scoredPointLogic(p, lastMoveDashRowCoordinate,
							lastMoveDashColumnCoordinate - MOVE_BY_ONE);
					hasScoredAPoint = true;
				}
				// check right square
				if (checkSidesOfASquare(lastMoveDashRowCoordinate,
						lastMoveDashColumnCoordinate, 'R')) {
					gameBoard.scoredPointLogic(p, lastMoveDashRowCoordinate,
							lastMoveDashColumnCoordinate + MOVE_BY_ONE);
					hasScoredAPoint = true;
				}
				if (hasScoredAPoint) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Check all other side of the selected (up, down, left or right square) if
	 * there's a dash
	 * 
	 * @param dashRow
	 *            - the row coordinate of the dash made in the last move
	 * @param dashColumn
	 *            - the column coordinate of the dash made in the last move
	 * @param squareToCheck
	 *            - char representing which square we check - up , down, left or
	 *            right
	 * @return boolean value - true if all the sides of the square we check have
	 *         dashes
	 */
	private boolean checkSidesOfASquare(int dashRow, int dashColumn,
			char squareToCheck) {
		squareToCheck = Character.toUpperCase(squareToCheck);
		if (squareToCheck == 'U') {

			// if all side of the upper square have a dash so there is a square
			if (gameBoard.checkIfSideHasDash(dashRow
					- MOVING_WITH_ONE_IN_DIRECTION, dashColumn
					- MOVING_WITH_ONE_IN_DIRECTION)
					&& gameBoard.checkIfSideHasDash(dashRow - 2, dashColumn)
					&& gameBoard.checkIfSideHasDash(dashRow
							- MOVING_WITH_ONE_IN_DIRECTION, dashColumn
							+ MOVING_WITH_ONE_IN_DIRECTION)) {

				return true;
			}
		} else if (squareToCheck == 'D') {
			// if all side of the down square have a dash so there is a square
			if (gameBoard.checkIfSideHasDash(dashRow
					+ MOVING_WITH_ONE_IN_DIRECTION, dashColumn
					- MOVING_WITH_ONE_IN_DIRECTION)
					&& gameBoard.checkIfSideHasDash(dashRow + 2, dashColumn)
					&& gameBoard.checkIfSideHasDash(dashRow
							+ MOVING_WITH_ONE_IN_DIRECTION, dashColumn
							+ MOVING_WITH_ONE_IN_DIRECTION)) {
				return true;
			}
		} else if (squareToCheck == 'R') {
			// if all side of the right square have a dash so there is a square
			if (gameBoard.checkIfSideHasDash(dashRow
					- MOVING_WITH_ONE_IN_DIRECTION, dashColumn
					+ MOVING_WITH_ONE_IN_DIRECTION)
					&& gameBoard.checkIfSideHasDash(dashRow, dashColumn + 2)
					&& gameBoard.checkIfSideHasDash(dashRow
							+ MOVING_WITH_ONE_IN_DIRECTION, dashColumn
							+ MOVING_WITH_ONE_IN_DIRECTION)) {

				return true;
			}
		} else if (squareToCheck == 'L') {
			// if all side of the left square have a dash so there is a square
			if (gameBoard.checkIfSideHasDash(dashRow, dashColumn - 2)
					&& gameBoard.checkIfSideHasDash(dashRow
							- MOVING_WITH_ONE_IN_DIRECTION, dashColumn
							- MOVING_WITH_ONE_IN_DIRECTION)
					&& gameBoard.checkIfSideHasDash(dashRow
							+ MOVING_WITH_ONE_IN_DIRECTION, dashColumn
							- MOVING_WITH_ONE_IN_DIRECTION)) {

				return true;
			}
		}
		return false;
	}

	/**
	 * check if the entered row/column by player is in game table ( array )
	 * 
	 * @param - integer number representing the coordinate you need to check
	 * @return true if it's in the array
	 */
	private boolean checkForCorrectMove(int num) {
		if (num < MIN_GAMEBOARD_DOT_POSSITION || num > gameBoard.getSize()) {
			System.out.println("Invalid input!");
			return false;
		}
		return true;

	}

	/**
	 * Modify the inputed coordinate of the dots, so they match the gameBoard
	 * coordinates
	 * 
	 * @param coordinate
	 * @return - integer number representing the actual coordinate of the dot in
	 *         the array
	 */
	private int modifyCoordinate(int coordinate) {
		return (coordinate - TO_MATCH_ARRAY_CORDINATES) * FREE_SPACE_FOR_DASHES;
	}

	/**
	 * Set number of the available moves
	 * 
	 * @param size
	 *            - integer representing the size of the game field ( not actual
	 *            size of the array) the size is the number of the dots rows or
	 *            dots columns
	 */
	private void calculateAvailableMoves(int size) {
		if (size > 0) {
			this.availableMoves = (size * size) * FREE_SPACE_FOR_DASHES
					- FREE_SPACE_FOR_DASHES * size;
		}
	}

	/**
	 * Set coordinates (row and column) of the dash made in the current move.
	 * 
	 * @param currentPointRow
	 *            - row coordinate of the current Point
	 * @param currentPointColumn
	 *            - column coordinate of the current Point
	 * @param currentPointSide
	 *            - direction from the current Point
	 */
	private void setLastDashCoordinates(int currentPointRow,
			int currentPointColumn, char currentPointSide) {
		currentPointSide = Character.toUpperCase(currentPointSide);
		if (currentPointSide == 'U') {
			this.lastMoveDashRowCoordinate = --currentPointRow;
			this.lastMoveDashColumnCoordinate = currentPointColumn;
		} else if (currentPointSide == 'D') {
			this.lastMoveDashRowCoordinate = ++currentPointRow;
			this.lastMoveDashColumnCoordinate = currentPointColumn;
		} else if (currentPointSide == 'R') {
			this.lastMoveDashRowCoordinate = currentPointRow;
			this.lastMoveDashColumnCoordinate = ++currentPointColumn;
		} else if (currentPointSide == 'L') {
			this.lastMoveDashRowCoordinate = currentPointRow;
			this.lastMoveDashColumnCoordinate = --currentPointColumn;
		}
	}
}